<template>
  <div class="container clearfix">
    <div class="top">
      <div class="input" :class="isReErr ? 'isErr' : ''">
        <el-input v-model="reInput">
          <template #prepend>/</template>
          <template #append>/{{ pattern.join("") }}</template>
        </el-input>
      </div>
      <!-- 文档 -->
      <div class="btns">
        <el-popover :visible="patternVisible" :width="360">
          <el-checkbox-group v-model="pattern">
            <el-checkbox v-for="item in patternList" :label="item.value" :key="item.value" @click.stop="">
              {{ item.label }}【<b>{{ item.value }}</b
              >】
            </el-checkbox>
          </el-checkbox-group>

          &nbsp;
          <div class="flex-center">
            <a
              target="_blank"
              href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions#%E9%80%9A%E8%BF%87%E6%A0%87%E5%BF%97%E8%BF%9B%E8%A1%8C%E9%AB%98%E7%BA%A7%E6%90%9C%E7%B4%A2"
            >
              <i class="iconfont icon-link"></i>
              MDN参考
            </a>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <a
              target="_blank"
              href="https://ann-yang.gitee.io/note-sites/guide/JavaScript/JavaScript.html#regular-expression"
            >
              <i class="iconfont icon-link"></i>
              笔记
            </a>
          </div>

          <template #reference>
            <el-button type="primary" @click.stop="toggleVisible('pattern')">
              <i class="iconfont icon-shuangmoshiqiehuan"></i>
              &nbsp;模式
            </el-button>
          </template>
        </el-popover>

        <el-popover :visible="syntaxVisible" :width="450" popper-class="regex-popover">
          <div class="descript-block" v-for="item in descriptList" :key="item.title">
            <div>
              <h3 class="title">{{ item.title }}</h3>
              <div class="content-item" v-for="(desc, i) in item.descriptList" :key="i" @click="useCopy(desc.code)">
                <span class="code">{{ desc.code }}</span>
                <span class="descript">{{ desc.descript }}</span>
              </div>
            </div>
          </div>

          <div class="flex-center">
            <a
              target="_blank"
              href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp"
            >
              <i class="iconfont icon-link"></i>
              MDN参考
            </a>
          </div>

          <template #reference>
            <el-button type="primary" @click.stop="toggleVisible('syntax')">
              <i class="iconfont icon-wendang"></i>
              &nbsp; 语法
            </el-button>
          </template>
        </el-popover>

        <el-popover :visible="exampleVisible" :width="500" popper-class="regex-popover">
          <div class="descript-block" v-for="item in exampleList" :key="item.title">
            <div>
              <h3 class="title">{{ item.title }}</h3>
              <div class="content-item" v-for="(desc, i) in item.descriptList" :key="i" @click="useCopy(desc.code)">
                <span class="code" style="flex: 55">{{ desc.code }}</span>
                <span class="descript">{{ desc.descript }}</span>
              </div>
            </div>
          </div>

          <template #reference>
            <el-button type="primary" @click.stop="toggleVisible('example')">
              <i class="iconfont icon-daimashili"></i>
              &nbsp; 示例
            </el-button>
          </template>
        </el-popover>

        <el-button type="primary" @click.stop="copyRe" :disabled="!(!!reInput && !!inputText)">
          <i class="iconfont icon-shengchengdaima"></i>
          &nbsp;复制正则
        </el-button>
      </div>
    </div>

    <div class="content">
      <div class="content-item">
        <h5>待匹配的文本</h5>
        <el-input v-model="inputText" type="textarea" placeholder="在此输入待匹配的文本" />
      </div>

      <div class="content-item">
        <el-button type="primary" @click="handle('test')" :disabled="!(!!reInput && !!inputText)"
          >测试【<b>test</b>】</el-button
        >
        <el-button type="primary" @click="handle('exce')" :disabled="!(!!reInput && !!inputText) && !!resultText"
          >匹配【<b>exce</b>】</el-button
        >
        <el-button type="primary" @click="handle('split')" :disabled="!(!!reInput && !!inputText)"
          >分割【<b>split</b>】</el-button
        >

        <div class="handle">
          <el-input v-model="replaceText" clearable placeholder="输入替换的文本" size="small">
            <template #append>
              <el-button @click="handle('replace')" :disabled="!(!!reInput && !!inputText && !!replaceText)"
                >替换【<b>replace</b>】</el-button
              >
            </template>
          </el-input>
        </div>
        <el-button type="primary" @click="resultText = ''" :disabled="!resultText">清空结果</el-button>
      </div>

      <div class="content-item">
        <!-- <h5>生成的正则: {{reConverted}}</h5> -->

        <div class="match">
          <v-md-editor v-model="resultText" mode="preview" height="96%"></v-md-editor>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted, onUnmounted, watch } from "vue";
import pkg from "root/package.json";
import { patternList, descriptList, exampleList } from "./const";
import useCopy from "@/hook/useCopy";
import { message } from "@/utils/Element";
import { addREPattern, reExecStr, getCountText } from "./util";

const { log, warn, group, groupEnd } = console;

const reInput = ref(""); // 输入的正则
const reConverted = ref<RegExp>(); // 转换为js正则
const pattern = ref(["g", "s"]); // 正则模式
const inputText = ref("hello1 world example text\n".repeat(4)); // 输入的文本
const replaceText = ref("^-^"); // 替换的文本
const resultText = ref(""); // 处理后的文本
const storeRe = pkg.name + "RE";

// popover 显隐
const patternVisible = ref(false);
const syntaxVisible = ref(false);
const exampleVisible = ref(false);

// 正则是否有错误
const isReErr = ref(false);

// 高亮类名
const highlightCls = ["green", "blue", "pink"];

onMounted(() => {
  const re = localStorage.getItem(storeRe);
  if (re) {
    reInput.value = re;
  } else {
    reInput.value = "world";
  }
});

// 切换 popover 显示
const toggleVisible = (flog: "pattern" | "syntax" | "example") => {
  switch (flog) {
    case "pattern":
      patternVisible.value = !patternVisible.value;
      syntaxVisible.value = exampleVisible.value = false;
      break;
    case "syntax":
      syntaxVisible.value = !syntaxVisible.value;
      patternVisible.value = exampleVisible.value = false;
      break;
    case "example":
      exampleVisible.value = !exampleVisible.value;
      patternVisible.value = syntaxVisible.value = false;
      break;
  }
};

const handlWindClick = () => {
  syntaxVisible.value = patternVisible.value = exampleVisible.value = false;
};
window.addEventListener("click", handlWindClick);

onUnmounted(() => {
  window.removeEventListener("click", handlWindClick);
});

const copyRe = () => {
  let res = generateRe();
  if (res instanceof RegExp) {
    useCopy(res.toString());
  } else {
    message.warning("非法的正则表达式");
  }
};

// 生成正则
const generateRe = () => {
  try {
    if (!reInput.value.trim()) {
      return new Error("正则不能为空");
    }

    const re = new RegExp(reInput.value, pattern.value.join(""));
    log("generate RegExp: ", re);
    return re;
  } catch (error: unknown) {
    if (error instanceof Error) {
      warn(error);
      return error;
    }
  }
};

// test
const handleTest = () => {
  const testRe = generateRe();
  console.log(testRe);
  if (testRe instanceof RegExp) {
    reConverted.value = testRe;
    isReErr.value = false;
    // 将匹配到的字符替换成 html
    group("test");
    log("Regexp: ", testRe);
    resultText.value = buildTestText(testRe);
    groupEnd();
  } else {
    isReErr.value = true;
  }
  groupEnd();
};

// 构建 test 结果 markdown(默认)
const buildTestText = (re: RegExp) => {
  let index = 0;
  let result = "";

  const argsList: any[] = [];
  result = inputText.value.replace(re, (...args: any[]) => {
    argsList.push(args);
    const cls = highlightCls[index % 3];
    const html = `<span class="${cls}">${args[0]}</span>`;
    index++;
    return html;
  });
  log("test args", argsList);
  if (index === 0) {
    result = `:sweat_smile: 没有找到匹配项`;
  } else {
    result = `#### 共匹配${index}项内容, 如下: \n${result}`;
  }

  return result;
};

// 监听改变高亮匹配项
watch(
  [reInput, inputText, pattern],
  ([nRe], [oRe]) => {
    if (nRe && nRe !== oRe) {
      localStorage.setItem(storeRe, nRe);
    }
    handleTest();
  },
  { immediate: true, deep: true }
);

// 按钮处理
const handle = (flog: "test" | "replace" | "exce" | "split") => {
  switch (flog) {
    case "test":
      handleTest();
      break;
    case "replace":
      handleReplace();
      break;
    case "split":
      handleSplit();
      break;
    case "exce":
      handleExce();
      break;
  }
};

// split
const handleSplit = () => {
  group("split");
  log("Regexp: ", reConverted.value);
  resultText.value = buildSplitText();
  groupEnd();
};

// 构建 split 结果 markdown
const buildSplitText = () => {
  const splitText = inputText.value.split(reConverted.value as RegExp);
  return `\`\`\`js
// split 
const splitText = \`${inputText.value}\`;
const splitRe = ${reConverted.value};
const result = splitText.split(splitRe);
console.log(JSON.stringify(result, null, 2));
\`\`\`


::: tip 共分割出${splitText.length}项结果, 如下
\`\`\`json
${JSON.stringify(splitText, null, 2)}
\`\`\`
:::
`;
};

// exce
const handleExce = () => {
  group("exce");
  resultText.value = buildExceText();
  groupEnd();
};

// 构建 exce 结果 markdown
const buildExceText = () => {
  // 添加g模式
  const re = addREPattern(reConverted.value as RegExp, "g");
  log("Regexp: ", re);
  const result = reExecStr(re, inputText.value);
  log("exce result: ", result);
  let dataText = "";

  result.forEach((item, i) => {
    const { index, length, groups, input } = item;
    dataText += `${i}: [
  0: \`${item[0]}\`,
  groups: ${JSON.stringify(groups, null, 2)}
  index: ${index}, 
  input: \`${getCountText(input)}\`, 
  length: ${length}, 
],

`;
  });

  return `\`\`\`js
// exce 
const exceText = \`${inputText.value}\`;
const exceRe = ${reConverted.value}; // 添加 g 模式, 防止下面死循环

let result = null;
const data = [];
while(result = exceRe.exec(exceText)) { 
  data.push(result);
}
console.log(data);
\`\`\`

::: tip 共匹配${result.length}项结果, 如下
\`\`\`js
${dataText}
\`\`\`
:::

`;
};

// replace
const handleReplace = () => {
  group("replace");
  log("Regexp: ", reConverted.value);
  resultText.value = buildReplaceText();
  groupEnd();
};

// 构建 replace 结果 markdown
const buildReplaceText = () => {
  let index = 0;
  const argsList: any[] = [];
  const text = inputText.value.replace(reConverted.value as RegExp, (...args: any[]) => {
    argsList.push(args);
    const cls = highlightCls[index % 3];
    const html = `<span class="${cls}">${replaceText.value}</span>`;
    index++;
    return html;
  });
  log("args", argsList);
  return `
\`\`\`js
// replace 
const text = \`${inputText.value}\`;
const replaceText = \`${replaceText.value}\`;
const replaceRe = ${reConverted.value};
const result = text.replace(replaceRe, replaceText);
console.log(result);
\`\`\`

#### 共替换了${index}项, 替换明细如下:
${text}

::: warning
  结果展示是 Markdown 格式, 替换时的格式要注意: 比如: *, **, -, = 等一些特殊符号会被显示为 Markdown,\n
  正确的格式需要转义, 例如: \`\\*\` 表示 \`*\`
:::
`;
};
</script>

<style lang="scss" scoped>
@import "@/styles/var.scss";

.container {
  margin: 20px auto 0 auto;
  padding: 0 80px;

  .top {
    width: 80%;
    display: flex;

    .input {
      flex: 5;
      min-width: 300px;
      :deep(.el-input) {
        .el-input__wrapper {
          box-shadow: 0 0 0 1px #dcdfe6 inset !important;
        }
      }
    }
    .isErr {
      :deep(.el-input) {
        .el-input__wrapper {
          box-shadow: 0 0 0 1px red inset !important;
          // .el-input__inner:hover {
          //   cursor: no-drop !important;
          // }
        }
      }
    }

    .btns {
      flex: 4;
      min-width: 400px;
      button {
        margin-left: 10px;
      }
    }
  }

  $height: 35vh;
  .content {
    .content-item {
      margin: 20px 0;

      h5 {
        margin-bottom: 6px;
      }
      :deep(.el-textarea > .el-textarea__inner) {
        height: calc($height / 2.5);
      }
      .handle {
        display: inline-flex;
        margin: 0 10px;
        width: $height;
        min-width: 350px;
      }

      .match {
        height: calc($height * 1.6);
        box-shadow: $slanting-shadow;
        border-radius: 4px;
        font-size: 16px;
        color: #606266;
        overflow-x: hidden;
        overflow-y: auto;
        outline: none;

        :deep(.vuepress-markdown-body) {
          padding: 4px 8px;
        }

        // 正则匹配高亮类
        :deep(.green),
        :deep(.blue),
        :deep(.pink) {
          border-radius: 4px;
          margin: 4px 2px;
          line-height: 1.6em;
        }

        :deep(.green) {
          background: #d9f8ea;
          box-shadow: 0 0 0 1px #42b883;
        }
        :deep(.blue) {
          background: #cae5ff;
          box-shadow: 0 0 0 1px #1789fa;
        }

        :deep(.pink) {
          background: #ffdfff;
          box-shadow: 0 0 0 1px #df56ad;
        }
      }
    }
  }
}
</style>
